package org.ponly.cta4j.factory;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import javax.imageio.ImageIO;
import javax.imageio.stream.MemoryCacheImageInputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream;
import org.ponly.cta4j.Captcha;

/**
 * 抽象验证码.
 *
 * @author vacoor
 * @since 1.0
 */
public abstract class AbstractCaptcha implements Captcha, Serializable {
    private static final long INEFFECTIVE = -1;

    /**
     * 验证码图片.
     */
    protected transient BufferedImage challenge;

    private boolean hasChallengeBeenCalled;

    private long effectiveTime = INEFFECTIVE;

    /**
     * 构建一个验证码.
     *
     * @param challenge 验证码图片
     */
    protected AbstractCaptcha(final BufferedImage challenge) {
        this.challenge = challenge;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public BufferedImage getChallenge() {
        hasChallengeBeenCalled = true;
        if (INEFFECTIVE == effectiveTime) {
            effectiveTime = System.currentTimeMillis();
        }
        return challenge;
    }

    /**
     * Dispose the challenge, once this method is call the getChallenge method
     * will return null.<br>
     * It has been added for technical reasons : a captcha is always used in a
     * two step fashion<br>
     * First submit the challenge, and then wait until the response arrives.<br>
     * It had been asked to have a method to dispose the challenge that is no
     * longer used after being dipslayed. So here it is!
     */
    @Override
    public final void disposeChallenge() {
        this.challenge = null;
    }


    /**
     * This method should return true if the getChalenge method has been called
     * (has been added in order to properly manage the captcha state.
     *
     * @return true if getChallenge has been called false otherwise.
     */
    @Override
    public boolean hasChallengeBeenCalled() {
        return hasChallengeBeenCalled;
    }

    /**
     * {@inheritDoc}
     *
     * @return
     */
    @Override
    public long getEffectiveTime() {
        return effectiveTime;
    }

    /**
     * This method have to be implemented in order to serialize the image challenge to PNG format.
     *
     * @param out The target outputStream in which the captcha will be serialized
     */
    private void writeObject(final ObjectOutputStream out) throws IOException {
        // Serialize captcha fields with defaut method
        out.defaultWriteObject();

        // If the challenge has not been disposed
        if (this.challenge != null) {
            // use png encoding
            ImageIO.write(this.challenge, "png", new MemoryCacheImageOutputStream(out));
        }
    }

    /**
     * This method have to be implemented in order to unserialize the image challenge from PNG format.
     *
     * @param in The source inputStream from which the captcha will be unserialized
     */
    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
        // UnSerialize captcha fields with default method
        in.defaultReadObject();

        try {
            this.challenge = ImageIO.read(new MemoryCacheImageInputStream(in));
        } catch (final IOException e) {
            if (!hasChallengeBeenCalled) {
                // If the getChallenge method has not been called the challenge
                // should be available for unmarhslling.
                // In this case, the thrown Exception is not related to the
                // dispose status
                throw e;
            }
        }
    }
}
